﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using r2mp_DataStructures;
using System.Drawing;
using System.Windows.Forms;

namespace r2mp_GUI
{

    /// <summary>
    /// Class used for drawing and scaling of PSP-Diagrams
    /// </summary>
    class PSPWorker : DiagramWorker
    {
        Task tasklist;
        float longestString;
        float highestTotalHeight;
        float taskWidth;
        float taskHeight;
        int deepestLevel;
        Font standardFont;
        Graphics graphics;
        private float SGap = 10;
        private float LGap = 20;
        Panel drawingPanel;
        float totalWidthPerColumn;
        float textSize = 8;

        /// <summary>
        /// Constructs a basic PSPWorker
        /// </summary>
        /// <param name="tasklist">The Root node of a tree built of Task Objects</param>
        /// <param name="drawingArea">The panel to draw the diagram onto</param>
        public PSPWorker(Task tasklist, Panel drawingArea)
        {
            this.tasklist = tasklist;
            this.drawingPanel = drawingArea;
        }

        /// <summary>
        /// Scales the content of the panel down by one step
        /// </summary>
        public void zoomOut()
        {
            if (textSize < 1) return;
            SGap = SGap * 0.9f;
            LGap = LGap * 0.9f;
            textSize = textSize * 0.9f;
        }

        /// <summary>
        /// Scales the content of the panel up by one step
        /// </summary>
        public void zoomIn()
        {
            if (textSize >= 72) return;
            SGap = SGap * 1.1f;
            LGap = LGap * 1.1f;
            textSize = textSize * 1.1f;
        }

        /// <summary>
        /// This method takes care of the essential tasks of drawing the PSP.
        /// </summary>
        /// <param name="graphics">The Graphics object to draw on</param>
        public void draw(Graphics graphics)
        {
            float x;
            float y;
            this.graphics = graphics;
            standardFont = new Font("Arial", textSize);
            longestString = 0;
            highestTotalHeight = 0;
            deepestLevel = 0;
            findLongestString(tasklist, graphics);
            findDeepestLevel(tasklist, 0);
            //The width of a task-box is the longest string plus the gap that has to be left at the border of the boxes
            taskWidth = longestString + 2 * SGap;
            //The height of a task-box is the highest string plus the gap that has to be left above every string
            taskHeight = highestTotalHeight + 2 * LGap;
            //The total width of a column is the width per task plus the deepest level of the tree
            //because every level that is gone down inside the tree leaves a certain gap to indicate the depth of each task
            totalWidthPerColumn = taskWidth + SGap * deepestLevel;
            //Calculates the necessary width for the whole panel based on the number of maintasks because every maintask
            //creates a column
            drawingPanel.Width = (int)LGap * 2 + (int)(totalWidthPerColumn + SGap) * tasklist.Tasks.Count;
            //Draws the parent task
            drawTask(tasklist, (totalWidthPerColumn) * (tasklist.Tasks.Count - 1) / 2 + LGap, LGap);
            x = (totalWidthPerColumn) * (tasklist.Tasks.Count - 1) / 2 + LGap + taskWidth / 2;
            y = LGap + taskHeight;
            graphics.DrawLine(Pens.Black, x, y, x, y + LGap / 2);
            graphics.DrawLine(Pens.Black, LGap + taskWidth / 2, y + LGap / 2, LGap + (tasklist.Tasks.Count - 1) * totalWidthPerColumn + taskWidth / 2, y + LGap / 2);
            int rowCount = 0;
            y = 2 * LGap + taskHeight;
            for (int i = 0; i < tasklist.Tasks.Count; i++)
            {
                int currentRowCount;
                x = LGap + i * (totalWidthPerColumn);
                drawTask(tasklist.Tasks[i], x, y);
                x += taskWidth / 2;
                graphics.DrawLine(Pens.Black, x, y, x, y - LGap / 2);
                currentRowCount = drawColumnOfSubTasks(i, 0, 1, tasklist.Tasks[i]);
                if (currentRowCount > rowCount) rowCount = currentRowCount;
            }
            drawingPanel.Height = (int)((2 * taskHeight) * (rowCount + LGap) + LGap);
        }

        /// <summary>
        /// Takes care of drawing one column of subtasks as part of a PSP-Diagram
        /// If the current subtask has it's own subtasks, this method is called recursively
        /// </summary>
        /// <param name="columnNumber">The number of the column used to calculate the horizontal position of the column,
        /// starts to count at 0</param>
        /// <param name="rowNumber">The number of the row at which the column starts, used to calculate the 
        /// vertical position of the column, starts to count at 0</param>
        /// <param name="depth">The depth how far the current tasklist is in the maintree of tasks, used to
        /// calculate the intendation of the column, starts at to count at 0</param>
        /// <param name="task">The tasklist which contains the complete subtask to be drawn</param>
        /// <returns>At which row number the method ended to draw, used to determine the vertical position at
        /// which the calling method has to keep drawing in case of an recursive call</returns>
        private int drawColumnOfSubTasks(int columnNumber, int rowNumber, int depth, Task task)
        {
            int startingRow = rowNumber;
            int endRow = rowNumber;
            float x;
            float y;
            bool hasSubTasks = false;
            foreach (Task currentTask in task.Tasks)
            {
                x = LGap + columnNumber * (totalWidthPerColumn) + (depth) * SGap;
                y = taskHeight * 2 + LGap * 3 + rowNumber * (LGap + taskHeight);
                drawTask(currentTask, x, y);
                y += taskHeight / 2;
                graphics.DrawLine(Pens.Black, x - SGap / 2, y, x, y);
                endRow = rowNumber;
                rowNumber++;
                if (task.Tasks.Count > 0)
                {
                    rowNumber = drawColumnOfSubTasks(columnNumber, rowNumber, depth + 1, currentTask);
                    hasSubTasks = true;
                }
            }
            if (hasSubTasks)
            {
                x = LGap + columnNumber * (totalWidthPerColumn) + (depth) * SGap - SGap / 2;
                y = taskHeight * 2 + LGap * 2 + startingRow * (LGap + taskHeight);
                graphics.DrawLine(Pens.Black, x, y, x, taskHeight * 2 + LGap * 3 + endRow * (LGap + taskHeight) + taskHeight / 2);
            }
            return rowNumber;
        }

        /// <summary>
        /// Draws one task onto the currently set graphics object
        /// </summary>
        /// <param name="task">The task to draw</param>
        /// <param name="x">The horizontal position of the task</param>
        /// <param name="y">The vertical position of the task</param>
        private void drawTask(Task task, float x, float y)
        {
            PointF[] stringLocations;
            List<String> strings = new List<String>();
            graphics.FillRectangle(Brushes.Beige, x, y, taskWidth, taskHeight);
            graphics.DrawRectangle(Pens.Black, x, y, taskWidth, taskHeight);
            if (task.Nr != null && task.Nr != String.Empty)
            {
                strings.Add(task.Nr);
            }
            if (graphics.MeasureString(task.Name, standardFont).Width > longestString)
            {
                strings.Add(task.Name.Substring(0, task.Name.Length / 2));
                strings.Add(task.Name.Substring(task.Name.Length / 2 + 1));
            }
            else
            {
                strings.Add(task.Name);
            }
            if (task.Ressources.Count > 0)
            {
                strings.Add(task.Ressources[0]);
            }
            stringLocations = calculateStringLocations(strings, x, y, taskHeight, taskWidth, graphics);
            for (int i = 0; i < strings.Count; i++)
            {
                graphics.DrawString(strings[i], standardFont, Brushes.Black, stringLocations[i]);
            }
            if (task.Status == Task.Started || task.Status == Task.Finished)
            {
                graphics.DrawLine(Pens.Black, x, y, x + taskWidth, y + taskHeight);
                if (task.Status == Task.Finished)
                {
                    graphics.DrawLine(Pens.Black, x, y + taskHeight, x + taskWidth, y);
                }
            }
        }

        /// <summary>
        /// Calculates the absolute positions of the strings inside the box used to draw the task into
        /// Even though the positions are calculated in relation to the containing panel and not the box they are supposed to
        /// be drawn into, it still takes the boundaries of the used boxes into account
        /// </summary>
        /// <param name="strings">The list of strings to be drawn</param>
        /// <param name="x">The horizontal position of the box to draw the string into</param>
        /// <param name="y">The vertical position of the box to draw the string into</param>
        /// <param name="taskHeight">The vertical size of the box to draw the string into</param>
        /// <param name="taskWidth">The horizontal size of the box to draw the string into</param>
        /// <param name="graphics">The graphics object used to measure the sizes of the strings</param>
        /// <returns></returns>
        private PointF[] calculateStringLocations(List<String> strings, float x, float y, float taskHeight, float taskWidth, Graphics graphics)
        {
            PointF[] resultSet = new PointF[strings.Count];
            SizeF[] sizes = new SizeF[strings.Count];
            float totalHeight = 0;
            float freeSpace;
            float avarageHeight;
            for (int i = 0; i < strings.Count; i++)
            {
                sizes[i] = graphics.MeasureString(strings[i], standardFont);
                totalHeight += sizes[i].Height;
            }
            freeSpace = (taskHeight - totalHeight - SGap) / strings.Count;
            avarageHeight = totalHeight / strings.Count;
            for (int i = 0; i < strings.Count; i++)
            {
                resultSet[i] = new PointF(x + taskWidth / 2 - sizes[i].Width / 2, y + (freeSpace + SGap) * i + SGap);
            }
            return resultSet;
        }

        /// <summary>
        /// Calculates the maximum depth of a tree of Task objects recursively
        /// </summary>
        /// <param name="tasklist">The Root node of the tree</param>
        /// <param name="level">The level at which the recursive calls are at</param>
        private void findDeepestLevel(Task tasklist, int level)
        {
            if (level > deepestLevel) deepestLevel = level;
            foreach (Task currentTask in tasklist.Tasks)
            {
                findDeepestLevel(currentTask, level + 1);
            }
        }

        /// <summary>
        /// Searches for the longest string inside the tree of tasks and stores it inside the respective property
        /// </summary>
        /// <param name="tasklist">The tree of tasks</param>
        /// <param name="graphics">The graphics object used to measure the strings</param>
        private void findLongestString(Task tasklist, Graphics graphics)
        {
            foreach (Task currentTask in tasklist.Tasks)
            {
                float totalHeight = 0;
                SizeF[] sizes = new SizeF[4];
                sizes[0] = graphics.MeasureString(currentTask.Nr, standardFont);
                sizes[1] = graphics.MeasureString(currentTask.Name, standardFont);
                sizes[1].Width = sizes[1].Width / 2;
                sizes[2] = graphics.MeasureString(currentTask.Name, standardFont);
                sizes[2].Width = sizes[2].Width / 2;
                if (currentTask.Ressources.Count == 0)
                {
                    sizes[3] = new SizeF(0, 0);
                }
                else
                {
                    sizes[3] = graphics.MeasureString(currentTask.Ressources[0], standardFont);
                }
                foreach (SizeF currentSize in sizes)
                {
                    if (currentSize.Width > longestString)
                    {
                        longestString = currentSize.Width;
                    }
                    totalHeight += currentSize.Height;
                }
                if (totalHeight > highestTotalHeight)
                {
                    highestTotalHeight = totalHeight;
                }
                if (currentTask.Tasks.Count > 0)
                {
                    findLongestString(currentTask, graphics);
                }
            }
        }

        public Panel DrawingPanel
        {
            set { drawingPanel = value; }
        }
    }
}
